//! Define unidata for control curd
//!
//! Because we will have json, csv and other data types

pub mod csv;
pub mod editor;
pub mod reader;

use std::{collections::HashMap, convert::TryInto, fs::File, io::Write, ops::Deref, path::Path};

use json::JsonValue;
use magic_query::{Map, Value};

use crate::{ForceUnwrap, INDEX_KEY};

/// it is a k-v type
///
/// the data will have a list of item
pub type Item = HashMap<String, json::JsonValue>;

/// Control this type
///
/// Do not worry about the difference of json/csv data type
#[derive(Clone, Debug)]
pub struct Data {
    pub(crate) _data: Vec<Item>,
}

impl Deref for Data {
    type Target = Vec<Item>;
    fn deref(&self) -> &Self::Target {
        &self._data
    }
}

impl Into<Map> for Data {
    fn into(self) -> Map {
        let list: Vec<HashMap<String, Value>> = self
            ._data
            .into_iter()
            .map(|item| {
                let mut map = HashMap::new();
                for (k, v) in item {
                    let value = match v {
                        JsonValue::Number(number) => {
                            if let Ok(n) = TryInto::<isize>::try_into(number) {
                                Value::from(n)
                            } else if let Ok(n) = TryInto::<i64>::try_into(number) {
                                Value::from(n)
                            } else {
                                Value::from(Into::<f32>::into(number))
                            }
                        }
                        JsonValue::String(s) => Value::from(s),
                        _ => Value::from("unknown_type"),
                    };

                    map.insert(k, value);
                }
                map
            })
            .collect();

        list.into()
    }
}

impl From<Vec<&Item>> for Data {
    fn from(data: Vec<&Item>) -> Self {
        let mut _data = Vec::new();
        for item in data {
            _data.push(item.clone());
        }
        Self { _data }
    }
}

impl From<Vec<Item>> for Data {
    fn from(data: Vec<Item>) -> Self {
        Self { _data: data }
    }
}

impl Into<Vec<Item>> for Data {
    fn into(self) -> Vec<Item> {
        self._data
    }
}

impl Into<Vec<HashMap<String, serde_json::Value>>> for Data {
    fn into(self) -> Vec<HashMap<String, serde_json::Value>> {
        let mut res = Vec::new();
        for data in self._data {
            let mut map = HashMap::new();
            for (k, v) in data {
                let v = if v.is_null() {
                    serde_json::Value::Null
                } else if let Some(v) = v.as_bool() {
                    serde_json::Value::from(v)
                } else if let Some(v) = v.as_usize() {
                    serde_json::Value::from(v)
                } else if let Some(v) = v.as_isize() {
                    serde_json::Value::from(v)
                } else if let Some(v) = v.as_f64() {
                    serde_json::Value::from(v)
                } else if let Some(v) = v.as_f32() {
                    serde_json::Value::from(v)
                } else if let Some(v) = v.as_i64() {
                    serde_json::Value::from(v)
                } else if let Some(v) = v.as_u64() {
                    serde_json::Value::from(v)
                } else {
                    serde_json::Value::from(v.as_str().unwrap())
                };
                map.insert(k, v);
            }

            res.push(map);
        }

        res
    }
}

impl AsRef<Vec<Item>> for Data {
    fn as_ref(&self) -> &Vec<Item> {
        &self._data
    }
}

impl Data {
    /// save file at any path
    pub fn save_at<P: AsRef<Path>>(&self, path: P) {
        let mut file = File::create(path).force_unwrap();
        let keys: Vec<String> = self._data[0]
            .keys()
            .filter(|s| !s.as_str().eq(INDEX_KEY))
            .map(|s| s.to_string())
            .collect();

        file.write(keys.join(",").as_bytes()).force_unwrap();
        file.write(b"\n").force_unwrap();
        for item in &self._data {
            file.write(
                keys.iter()
                    .map(|s| format!("{}", item.get(s).force_unwrap()))
                    .collect::<Vec<String>>()
                    .join(",")
                    .as_bytes(),
            )
            .force_unwrap();
            file.write(b"\n").force_unwrap();
        }
    }
}
